local function find_esp32_idf_ver(root, no_print)
	local esp_idf_path = make_path(root, "components/esp_common/include/esp_idf_version.h");
	if utils:file_time(esp_idf_path) then
		-- esp version file found
		local version_content = load_file(esp_idf_path);
		local version_content = {version_content:match("define%s+ESP_IDF_VERSION_MAJOR%s+(%d+).-define%s+ESP_IDF_VERSION_MINOR%s+(%d+).-define%s+ESP_IDF_VERSION_PATCH%s+(%d+)")};
		if version_content[1] then
			if not no_print then
				print(string.format("ESP32-IDF v%u.%u.%u found at %s",version_content[1], version_content[2], version_content[3], root));
			end;
			return table.unpack(version_content);
		end;
	end;
end;

local esp_target_map = {
	["esp32"] = {
		define = "-D__xtensa__=1 -D__XTENSA__=1 -D__XTENSA_EL__=1";
		gdb_type = "xtensa-esp-elf-gdb";
	};
	["esp32c2"] = {
		define = "-D__riscv=1 -D__riscv_mul=1 -D__riscv_div=1 -D__riscv_muldiv=1 -D__riscv_xlen=32 -D__riscv_compressed=1 -D__riscv_cmodel_medlow=1 -D__riscv_float_abi_soft=1";
		gdb_type = "riscv32-esp-elf-gdb";
	};
	["esp32c3"] = {
		define = "-D__riscv=1 -D__riscv_mul=1 -D__riscv_div=1 -D__riscv_muldiv=1 -D__riscv_xlen=32 -D__riscv_compressed=1 -D__riscv_cmodel_medlow=1 -D__riscv_float_abi_soft=1";
		gdb_type = "riscv32-esp-elf-gdb";
	};
	["esp32h2"] = {
		define = "-D__riscv=1 -D__riscv_mul=1 -D__riscv_div=1 -D__riscv_muldiv=1 -D__riscv_xlen=32 -D__riscv_compressed=1 -D__riscv_cmodel_medlow=1 -D__riscv_float_abi_soft=1";
		gdb_type = "riscv32-esp-elf-gdb";
	};
	["esp32s2"] = {
		define = "-D__xtensa__=1 -D__XTENSA__=1 -D__XTENSA_EL__=1";
		gdb_type = "xtensa-esp-elf-gdb";
	};
	["esp32s3"] = {
		define = "-D__xtensa__=1 -D__XTENSA__=1 -D__XTENSA_EL__=1";
		gdb_type = "xtensa-esp-elf-gdb";
	};
};

local function find_esp32_idf(suggested_root)
	local repo = make_path(os.getenv("USERPROFILE"), ".espressif");
	local idf_env_json = make_path(repo, "idf-env.json");
	if utils:file_time(idf_env_json) == nil then
		return nil;
	end;
	local idf_env = load_json(idf_env_json) or {idfInstalled={}};
	idf_env = idf_env.idfInstalled[idf_env.idfSelectedId];
	local tool_env = nil;
	local root = nil;
	local bin = nil;
	local openocd_root = nil;
	local cmake_path = nil;
	local ninja_path = nil;
	local idf_targets = nil;
	local esp_ver_major = 0;
	local esp_ver_minor = 0;
	local esp_ver_patch = 0;
	if idf_env then
		-- ESP32-IDF installed
		if suggested_root then
			esp_ver_major, esp_ver_minor, esp_ver_patch = find_esp32_idf_ver(suggested_root);
			if esp_ver_major then
				root = suggested_root;
			end;
		end;
		if root == nil and idf_env.path then
			esp_ver_major, esp_ver_minor, esp_ver_patch = find_esp32_idf_ver(idf_env.path);
			if esp_ver_major and utils:file_time(idf_env.path) ~= nil then
				root = idf_env.path;
			end;
		end;
		if root == nil and os.env("IDF_PATH") then
			local env_idf_path = os.env("IDF_PATH");
			esp_ver_major, esp_ver_minor, esp_ver_patch = find_esp32_idf_ver(env_idf_path);
			if esp_ver_major and utils:file_time(env_idf_path) ~= nil then
				root = env_idf_path;
			end;
		end;
		if root == nil then
			-- find the root
			for idx, drive in ipairs({"C:\\", "D:\\", "E:\\", "F:\\", "G:\\"}) do
				local name = utils:list_dir(make_path(drive, "esp-idf*"));
				if name then
					local esp_root = make_path(drive, name);
					esp_ver_major, esp_ver_minor, esp_ver_patch= find_esp32_idf_ver(esp_root);
					if esp_ver_major then
						root = esp_root;
						break;
					end;
				end;
			end;
		end;
		if root == nil then
			return nil;
		end;
		local tools_json = make_path(root, "tools/tools.json");
		if utils:file_time(tools_json) == nil then
			return nil;
		end;
		tools_json = load_json(tools_json);
		local esp_tools = tools_json.tools or {};
		tool_env = {};
		bin = {};
		local all_idf_targets = {};
		for idx, tool in ipairs(esp_tools) do
			local tool_root = make_path(repo, "tools", tool.name, tool.versions[1].name);
			if tool.export_paths then
				for idx, path in ipairs(tool.export_paths) do
					local tool_path = make_path(tool_root, table.concat(path, "/"));
					if utils:file_time(tool_path) then
						table.insert(bin, tool_path);
						if tool.name == "openocd-esp32" then
							openocd_root = tool_path;
						elseif tool.name == "cmake" then
							cmake_path = make_path(tool_path, "cmake.exe");
							if utils:file_time(cmake_path) then
								table.insert(CMAKE_PATHS, cmake_path);
							else
								cmake_path = nil;
							end;
						elseif tool.name == "ninja" then
							ninja_path = make_path(tool_path, "ninja.exe");
							if utils:file_time(ninja_path) then
								table.insert(NINJA_PATHS, ninja_path);
							else
								ninja_path = nil;
							end;
						end;
						if string.match(tool.name, ".*%-esp%-elf%-gdb") then
							-- gdb found
							for idx, target in ipairs(tool.supported_targets) do
								esp_target_map[target].gdb_root = make_path(tool_path, "..");
								esp_target_map[target].gdb_name = tool.name;
							end;
						elseif string.match(tool.name, "xtensa%-esp.*%-elf$") then
							-- compiler found
							for idx, target in ipairs(tool.supported_targets) do
								all_idf_targets[target] = true;
								if not esp_target_map[target].root then
									esp_target_map[target].root = make_path(tool_path, "..");
									esp_target_map[target].name = tool.name;
								end;
							end;
						elseif tool.name == "riscv32-esp-elf" then
							-- compiler found
							for idx, target in ipairs({"esp32c2", "esp32c3", "esp32h2"}) do
								all_idf_targets[target] = true;
								if not esp_target_map[target].root then
									esp_target_map[target].root = make_path(tool_path, "..");
									esp_target_map[target].name = tool.name;
								end;
							end;
						end;
					end;
				end;
			end;
			if tool.export_vars then
				for name, value in pairs(tool.export_vars) do
					tool_env[name] = value:gsub("%${(.-)}", {
							["TOOL_PATH"] = tool_root;
						});
				end;
			end;
		end;
		local python_name = utils:list_dir(make_path(repo, string.format("python_env/idf%u.%u_py*", esp_ver_major, esp_ver_minor)));
		tool_env["IDF_PYTHON_ENV_PATH"] = make_path(repo, "python_env", python_name, "Scripts");
		table.insert(bin, 1, tool_env["IDF_PYTHON_ENV_PATH"]);
		tool_env["IDF_PATH"] = root;
		tool_env["OPENOCD_ROOT"] = openocd_root;
		tool_env["CCACHE_DIR"] = edx.make_config_path("ccache", 0)
		idf_targets = {};
		for key, val in pairs(all_idf_targets) do
			table.insert(idf_targets, key);
		end;
		table.sort(idf_targets);
	end;

	if not idf_targets or #idf_targets == 0 then
		print("\x1b[33;41mESP32-IDF was not installed! Please run "..make_path(root, "install.bat").."\x1b[0m");
		return nil;
	end

	local target_info = {};
	for idx, target in pairs(idf_targets) do
		local esp_target = esp_target_map[target];
		local fullname = esp_target["name"];
		local target_root = esp_target["root"];
		local target_gdb_type = esp_target["gdb_type"];
		local target_name = utils:list_dir(make_path(target_root, "lib/gcc/*"));
		local target_ver = utils:list_dir(make_path(target_root, "lib/gcc", target_name, "*"));
		local gdb_home = make_path(repo, "tools", target_gdb_type);
		local gdb_ver = utils:list_dir(make_path(gdb_home, "*"));
		local gdb_path = make_path(gdb_home, gdb_ver, target_gdb_type, "bin", fullname.."-gdb.exe");
		target_info[target] = {
			root = target_root;
			name = target_name;
			version = target_ver;
			define = esp_target["define"];
			gdb_path = gdb_path;
			include = {
				make_path(target_root, "include");
				make_path(target_root, "lib/gcc", target_name, target_ver, "include");
				make_path(target_root, "lib/gcc", target_name, target_ver, "include-fixed");
				make_path(target_root, target_name, "sys-include");
				make_path(target_root, target_name, "include");
				make_path(target_root, target_name, "include/c++", target_ver);
			};
		};
	end;
	local ep32_idf = {
		["type"] = "esp32-idf";
		["name"] = "esp32-idf";
		["version"] = string.format("%u.%u.%u", esp_ver_major, esp_ver_minor, esp_ver_patch);
		["root"] = root;
		["repo"] = repo;
		["envs"] = tool_env;
		["bin"] = bin;
		["include"] = {};
		["lib"] = {};
		["targets"] = idf_targets;
		["tools"] = {
			["debugger"] = "openocd";
			["idf"] = "python " .. make_path(root, [[tools\idf.py]]);
			["esptool"] = "python " .. make_path(root, [[components\esptool_py\esptool\esptool.py]]);
			["espefuse"] = "python " .. make_path(root, [[components\esptool_py\esptool\espefuse.py]]);
			["otatool"] = "python " .. make_path(root, [[components\app_update\otatool.py]]);
			["parttool"] = "python " .. make_path(root, [[components\partition_table\parttool.py]]);
			["openocd"] = "openocd";
			["cmake"] = cmake_path;
			["ninja"] = ninja_path;
		};
		["target_info"] = target_info;
		["query_driver"] = function(self, cmd)
			local toolset = cmd.toolset;
			local target = toolset.target;
			local target_info = self.target_info[target];
			local target_root = target_info.root;
			local gcc_file = utils:list_dir(make_path(target_root, "bin/*-gcc.exe"));
			return make_path(target_root, "bin", gcc_file);
		end;
		encoding = "utf-8";
	};
	return ep32_idf;
end;
ESP32_IDF_TOOLSET = find_esp32_idf();
ESP32_IDF_CMAKE_PROJECT = false;

function esp32_idf_create_project(toolset, path, name, target, on_finished_handler)
	local cmds = {[[cmd /d /c ]]..toolset.tools.idf..[[ create-project -p "]]..path..[[" ]] .. name};
	if target ~= "esp32" then
		table.insert(cmds,
			[[cmd /d /c ]]..toolset.tools.idf..[[ set-target -C "]]..path..[[" ]] .. target
		);
	end;
	local tasks = {
		desc = "ESP32-IDF create project";
		envs = table.clone( toolset.envs, {
				PATH = table.concat(toolset.bin, ";")..";"..os.getenv("PATH");
		});
		["cmds"] = cmds;
		on_output = function(cmd_idx, output_line) end;
		on_command_begin = function(cmd_idx)
			print(cmds[cmd_idx]);
		end;
		on_command_end = function(cmd_idx, ret_code)
		end;
		on_finished = function(success)
			if on_finished_handler then
				on_finished_handler(success);
			end;
		end;
		encoding = "ansi";
	};
	edx:do_build(tasks)
end;

function esp32_idf_set_target(target, config, callback)
	if not target then
		target = "esp32";
	end;
	local sdkconfig = make_path(CMAKE_PROJECT_PATH, "sdkconfig");
	local sdkconfig_old = make_path(CMAKE_PROJECT_PATH, "sdkconfig.old");
	if utils:file_time(sdkconfig_old) then
		os.remove(sdkconfig_old);
	end;

	if utils:file_time(sdkconfig) then
		os.rename(sdkconfig, sdkconfig..".old");
	end;
	local cmd = find_build_command(nil, true);
	local do_build_params = build_cmake_do_build_params(cmd, nil, callback);
	do_build_params.desc = "ESP32-IDF set-target";
	do_build_params.encoding = "ansi";
	edx:do_build(do_build_params)
end;

function esp32_idf_show_serial_monitor()
	local toolset = ESP32_IDF_TOOLSET;
	local cmd_line = [[cmd /d /k ]]..toolset.tools.idf..[[ monitor"]];
	local params = table.clone( toolset.envs, {
			PATH = table.concat(toolset.bin, ";")..";"..os.getenv("PATH");
	});
	params[1] = CMAKE_PROJECT_PATH;
	edx.shell2(cmd_line, "*", params);
end;

function esp32_idf_show_command_line(path)
	local toolset = ESP32_IDF_TOOLSET;
	local cmd_line = [[cmd /d /k ]].. make_path(toolset.root, "export.bat");
	if not path then
		path = CMAKE_PROJECT_PATH;
	end;
	edx.shell2(cmd_line, "*", path);
end;
-- print_table(ESP32_IDF_TOOLSET);

function find_build_command_cmake(build_target)
	local config = menu_bar.get_cmake_config();
	local toolset = nil;
	local target = menu_bar.get_cmake_cpu() or "esp32";
	local cmake_extra_arguments = nil;
	if ESP32_IDF_CMAKE_PROJECT then
		local esp_toolset = ESP32_IDF_TOOLSET;
		-- is ESP32-IDF project
		local target_info = esp_toolset.target_info[target];
		toolset = table.clone(esp_toolset, {
				["name"] = esp_toolset.name.."-"..target;
				["target"] = target;
				["include"] = target_info.include;
				["define"] = target_info.define;
				["envs"] = table.clone(esp_toolset.envs, {
						["ESPPORT"] = menu_bar.get_cmake_serial_port() or (({utils:list_serial_ports()})[1] or {})[1];
						["ESPBAUD"] = menu_bar.get_cmake_serial_baud();
				});
				["tools"] = table.clone(esp_toolset.tools, {
						["gdb"] = target_info.gdb_path;
				});
		});
		if build_target == "install" then
			build_target = "flash";
		end;
		cmake_extra_arguments = " -DIDF_TARGET=\""..target.. "\" -DCCACHE_ENABLE=1";
	else
		toolset = menu_bar.get_cmake_toolset(true);
	end;

	local build_cmd = find_build_command_cmake_impl(build_target, toolset, config, cmake_extra_arguments);
	if build_cmd == nil then
		return nil;
	end;
	-- print_table(build_cmd);
	build_cmd.__esp_build_target = build_target;
	return build_cmd;
end;

local build_cmake_do_build_params_impl = build_cmake_do_build_params;
function build_cmake_do_build_params(cmd, build_type, on_complete, prepend_cmds, apppend_cmds)
	if cmd.__esp_build_target == "flash" then
		local com_port = cmd.toolset.envs["ESPPORT"];
		-- if the corresponding port was opened, trying to stop the serial port monitor
		if edx:set_serial_monitor(com_port, false) then
			-- the com port is closed successfully
			local old_on_complete = on_complete;
			on_complete = function(success)
				-- trying to reopen the com port
				edx:set_serial_monitor(com_port, true);
				if old_on_complete then
					old_on_complete(success);
				end;
			end;
		end;
	end;
	return build_cmake_do_build_params_impl(cmd, build_type, on_complete, prepend_cmds, apppend_cmds);
end;

menu_bar._cmake_target_filter = function(item)
	return ESP32_IDF_CMAKE_PROJECT and item.name:sub(1, 6) == "__idf_";
end;

local function esp32_idf_find_current_target(dir)
	local sdkconfig = make_path(dir, "sdkconfig");
	if utils:file_time(sdkconfig) then
		local text = load_file(sdkconfig);
		return string.match(text, [[CONFIG_IDF_TARGET=%"(.-)%"]]) or "esp32";
	end;
	return "esp32";
end;

table.insert(xws_mgr._pre_load_dir,
	function(self, dir)
		local project_cmake = make_path(dir, "CMakeLists.txt");
		if not utils:file_time(project_cmake) then
			return false;
		end;
		local data = load_file(project_cmake);
		ESP32_IDF_CMAKE_PROJECT = string.match(data, [[include%(%$ENV{IDF_PATH}]]) ~= nil;
		if ESP32_IDF_CMAKE_PROJECT then
			if ESP32_IDF_TOOLSET == nil then
				-- trying find esp32-idf in parent folders
				local root = make_path(dir, "..");
				while root and #root > 3 do
					local major_ver = find_esp32_idf_ver(root, true);
					if major_ver ~= nil then
						ESP32_IDF_TOOLSET = find_esp32_idf(root);
						break;
					end;
					local name = utils:list_dir(make_path(root, "esp-idf*"));
					if name then
						name = make_path(root, name);
						local major_ver = find_esp32_idf_ver(name, true);
						if major_ver ~= nil then
							ESP32_IDF_TOOLSET = find_esp32_idf(name);
							break;
						end;
					end;
					root = make_path(root, "..");
				end;
				if ESP32_IDF_TOOLSET == nil then
					-- esp32-idf still not found
					ESP32_IDF_CMAKE_PROJECT = false;
					print("\x1b[33;41m", LANG("ESP32-IDF not found!"),"\x1b[0m");
					print("\x1b[33;41m", LANG("Download from https://dl.espressif.cn/dl/esp-idf"),"\x1b[0m");
					return;
				end;
			end;
			local esp_toolset = ESP32_IDF_TOOLSET;
			menu_bar.hide_cmake_items("toolset");
			menu_bar.show_cmake_items(
				table.unpack({
						"serial_ports",
						"serial_baud",
						"cpus",
						"debug_adapters",
						-- "boards",
			}));
			menu_bar.set_cmake_text(LANG("menu_bar/cmake/esp32_idf"));
			local def_target = esp32_idf_find_current_target(dir);
			menu_bar.update_cmake_cpu(esp_toolset.targets, def_target);
			menu_bar.update_cmake_serial_baud(
				SERIAL_BAUDRATES_HI,
				SERIAL_DEFAULT_BAUDRATE
			);
			menu_bar.update_cmake_serial_port(
				function()
					local ports = {utils:list_serial_ports()};
					table.sort(ports, function(a, b)
							return tonumber(a[1]:sub(4)) < tonumber(b[1]:sub(4));
					end);
					return ports;
				end
			);
			menu_bar.update_cmake_debug_adapter({"esp_usb_jtag", "esp_usb_bridge", "jlink", "ftdi/esp32_devkitj_v1", "ftdi/esp32s2_kaluga_v1"});
		end;
		return false;
	end
);

table.insert(xws_mgr._post_unload_dir,
	function(self, dir)
		if ESP32_IDF_CMAKE_PROJECT then
			menu_bar.hide_cmake_items("serial_ports", "serial_baud", "debug_adapters", "cpus", "boards");
			menu_bar.show_cmake_items("toolset");
			menu_bar.set_cmake_text(LANG("menu_bar/cmake"));
		end;
		ESP32_IDF_CMAKE_PROJECT = false;
		return false;
	end
);
-- esp32_idf_create_project(ESP32_IDF_TOOLSET, [[d:\projects\test-esp32]], [[test-esp32]])
